function [] = xmlconCCs2EAPMW_dev()
  % GUI tool to move calibration coefficients given in a SeaBird xmlcon
  % file (database blob or disk file) to the appropriate tables in EAPMW

    DB = DBwrapper_dev('EAPMW');
  % Generic Input object for reuse
    if isempty(DB.err)
      % Ask user for location of  xmlcon file
        reply = inputdlg(['Is the xmlcon file a disk file (d) or a database '...
                       'BLOB (b)?'], 'XMLCON File Origin', 1, {'b'}, 'on');
        if strcmp(reply, 'b')
          % Generate list of available CONfileBLOBs
            try
                q = query_dev(DB, struct('tbls', {{'MatlabCTDProcessing'}},...
                                   'cols', {{'CONFileName', 'CONFileBLOB'}}...
                                   ));
                q.submitStatement
                if ~isempty(q.errCode) 
                    error(['Sorry, "' [q.errCode{:}] '" trying to open '...
                           'EAPMW; this error is fatal, aborting.'])
                end
                bad = cellfun(@isempty, q.result(:,2)) | ...
                      strcmp(q.result(:,2), 'null') | ...
                      cellfun(@isempty, strfind(q.result(:,2), '<?xml'));
                q.result = q.result(~bad,:);
                [~, where, ~] = unique(q.result(:,2));
                uniqueBlobs = q.result(where,2);
                uniqueBlobFNs = q.result(where,1);
                blobListPanel = uipanel('Title', ['Available BLOBs and '...
                                                  'their names in the '...
                                                  'database'],...
                                        'FontSize', 11);
                set(gcf, 'DefaultUicontrolUnits', 'Normalized')
                fP = get(gcf, 'Position');
                if isdeployed
                    set(blobListPanel, 'Visible', 'off')
                else
                    set(blobListPanel, 'Visible', 'on')
                end
                usageText = uicontrol('Parent', blobListPanel,...
                                      'Style', 'text',...
                                      'Position', [-0.05 0.9 1.1 0.075],...
                                      'String', ['Usage note: you must '...
                                      'first left-click on the desired '...
                                      'BLOB, then right-click to select '...
                                      'the action to perform.'],...
                                      'FontSize', 9);
                uTE = get(usageText, 'Extent');
                set(gcf, 'Position', [1 1 1.05*uTE(3) 1] .* fP)
                fnList = uicontrol('Parent', blobListPanel,...
                                   'Style', 'listbox',...
                                   'Position', [0.05 0.15 0.4 0.7],...
                                   'String', uniqueBlobFNs);
                blobList = uicontrol('Parent', blobListPanel,...
                                     'Style', 'listbox',...
                                     'Position', [0.55 0.15 0.4 0.7],...
                                     'String', uniqueBlobs);
                doneButton = uicontrol('Parent', blobListPanel,...
                                       'Style', 'pushbutton',...
                                       'Position', [0.45 0.03 0.1 0.08],...
                                       'HorizontalAlignment', 'Center',...
                                       'String', 'Done',...
                                       'Callback', {@(varargin) delete(get(...
                                                 blobListPanel, 'Parent')),...
                                                 @uiresume});
                set(blobListPanel, 'Visible', 'on')
                f = fileWrapper_dev('.\temp.xmlcon', 'w');
                ucm = uicontextmenu;
                m1 = uimenu(ucm, 'Label', 'Write', 'Callback', {@Write,...
                            uniqueBlobs{get(blobList, 'Value')}, f});
                m2 = uimenu(ucm, 'Label', 'Examine', 'Callback', {@Examine,...
                            uniqueBlobs{get(blobList, 'Value')}, f});
                set(blobList, 'uicontextmenu', ucm)
                uiwait
            catch ME
                errordlg(ME.message, 'DB Error!', 'modal')
                cleanUp;
                return
            end
        else
          % Get file
            [fn, pn, ~] = uigetfile('*.xmlcon', ['Please choose one or more '...
                                    'xmlcon files'], '', 'MultiSelect', 'on');
            f = fileWrapper_dev(fullfile(pn, fn), 'r');
        end
        tmp = myReadXMLFile(f.name);
        switch 1
            case regexp(tmp.Instrument_Name', 'SBE 25')
                p = ProcessSBE25xmlcon_dev(f);
            otherwise
                p = 'Sorry, not yet implemented';
        end
        if ismethod(p, 'unpackXMLCON') && isempty(p.err)
            p.unpackXMLCON;
        else
            errordlg(p, 'NYI Error!', 'modal')
            cleanUp;
            return
        end
        f.close;
        if strcmp(f.permission, 'w')
            f.delete;
        end
      % Get the EquipmentTypeID's and Descriptions already in the DB
        try
            q.setPropVals('tbls', {'EquipmentType'});
            q.setPropVals('cols', {'*'});
            q.submitStatement;
            N = size(q.result,1);
            [ETIDs{1:N}, ETDs{1:N}] = deal(q.result{:});
            q.result = {};
        catch ME
            errordlg(ME.message, 'Sorry, unhandled DB Error, Aborting!',...
                     'modal')
            cleanUp;
            return
        end
        in = struct();
      % For each sensor in the xmlcon
        for S = fieldnames(p.Sensors)', S = S{:};
            in.sen = S;
            status = 0; % Reset "new" indicator to false...
            ETID = 0; % and ID's to 0
            EID = 0;
            CID = 0;
            CCID = 0;
            tmp = cellfun(@regexp, ETDs, constCellStrArr(S(1:3),size(ETDs)),...
                          'UniformOutput', 0);
            idx = find(~cellfun(@isempty, tmp));
          % Is it already in the EquipmentType Table?
          % Yes:
            if ~isempty(idx)
                ETID = ETIDs{idx}; % get its ETID
          % No: 
            else
              % Implement:
              % 0) Report new parameter, ask if user would like to add it
              %    to the DB presently
                reply = questdlg(msg, title, 'modal');
                if strcmp(reply, 'No')
              % 1) No: continue on to next Sensor
                    continue
                else
              % 2) Yes: Query for new EquipmentTypeID and
              %    EquipmentTypeDescription (providing list of extant values
              %    with instructions not to duplicate); confirm that choices
              %    don't duplicate extant values; push new values into DB;
                    ETID = newETID;
                end
            end
            
          % Now, if status = 0, match on p.Sensors.(S).SerialNumber and 
          % ETID to get the Equipment.EquipmentID.  If status = 1 or serial
          % number is not found, offer to push the new serial number, 
          % continuing to the next sensor if user declines.
            in.des = 'serial number';
            if isnumeric(p.Sensors.(S).SerialNumber)
                p.Sensors.(S).SerialNumber = ...
                    num2str(p.Sensors.(S).SerialNumber);
            end
            in.val = p.Sensors.(S).SerialNumber;
            oldCond = ['EquipmentTypeID=''' ETID ''''];
            newCond = ['SerialNumberDescription=''' in.val ''''];
            wholeCond = [oldCond ' AND ' newCond];
            q.setPropVals('tbls', {'Equipment'})
            q.setPropVals('cols', {'EquipmentID'})
            q.setPropVals('where', {wholeCond})
            if status==0 % ETID was not new
                try
                    q.submitStatement
                catch ME
                    processCatch(q, ME)
                    continue
                end
                if isempty(q.result)
                    in.ttl = 'Serial Number Not Found';
                    in.msg = ['The ' in.des ' ' in.val ' for the ' S ' in '...
                              'the selected xmlcon was not found in the DB. '];
                    [result, in] = newORtypo(in);
                    if isempty(result)
                        continue
                    else
                        SN = result{:};
                        if isnumeric(SN)
                            SN = num2str(SN);
                        end
                    end
                else
                    EID = q.result{:}; q.result = {};
                end
            end
            if EID == 0
                if status==1 ... ETID was new => serial # must be new
                  || strcmp(SN, in.val) % user indicated SN is new 
                    I = Insert_dev(DB, struct('tbls', {{'Equipment'}}));
                    in.wholeCond = wholeCond;
                    q.setPropVals('where', {oldCond})
                    status = processNew(I, q, in);
                    I.errCode = {}; q.errCode = {};
                    if status
                        EID = q.result{:}; q.result = {};
                    else
                        msgbox(['Sorry, attempt to insert new ' in.des ...
                                ' failed; skipping to next sensor']);
                        continue
                    end
                else % status = 0 & in.val changed => typo in existing val
                    where = ['EquipmentTypeID=''' ETID ''' AND '...
                             'SerialNumberDescription=''' SN ''''];
                    try
                        q.setPropVals('where', {where})
                        q.submitStatement
                    catch ME
                        processCatch(q, ME)
                        continue
                    end
                    if isempty(q.result)
                        in.val = SN;
                        in.ttl = 'Serial Number Still Not Found';
                        secondFailure(in)
                        continue
                    else
                        EID = q.result{:}; q.result = {};
                    end
                end
            end
            
          % Now, if status = 0, match on p.Sensors.(S).CalibrationDate and
          % EID to get the Calibration.CalibrationID.  If status = 1 or 
          % calib. date not found, offer to push the new calib. date, 
          % continuing to the next sensor if user declines.
          % Note: since datetime matching can get hairy, we may need to do
          % this using a GUI, but let's try it w/out one first
            in.SN = in.val;
            in.des = 'calibration date';
            in.val = p.Sensors.(S).CalibrationDate';
            DT = datestr(in.val, 'YYYY-mm-DD');
            oldCond = ['EquipmentID=''' num2str(EID) ''''];
          % Use VARCHAR, not DATETIME, to avoid latter's inconsistent 
          % treatment of one digit days
            newCond = ['CONVERT(VARCHAR,UTCCalibrationBeginDatetime,120) '...
                       'LIKE ''' DT '%'''];
            wholeCond = [oldCond ' AND ' newCond];
            q.setPropVals('tbls', {'Calibration'})
            q.setPropVals('cols', {'CalibrationID'})
            q.setPropVals('where', {wholeCond})
            if status==0 % EID was not new
                try
                    q.submitStatement
                catch ME
                    processCatch(q, ME)
                    continue
                end
                if isempty(q.result)
                    in.ttl = 'Calibration Date Not Found';
                    in.msg = ['The ' in.des ' ' in.val ' for the ' S ... 
                              ', serial number ' p.Sensors.(S).SerialNumber ...
                              ' in the selected xmlcon was not found in '...
                              'the DB. '];
                    [result, in] = newORtypo(in);
                    if isempty(result)
                        continue
                    else
                        DT = result{:};
                    end
                else
                    CID = q.result{:}; q.result = {};
                end
            end
            if CID == 0
                if status==1 ... EID was new => calib. date must be new
                  || strcmp(DT, in.val) % user indicated CD is new 
                    I = Insert_dev(DB, struct('tbls', {{'Calibration'}}));
                    in.wholeCond = wholeCond;
                    q.setPropVals('where', {oldCond})
                    status = processNew(I, q, in);
                    I.errCode = {}; q.errCode = {};
                    if status
                        CID = q.result{:}; q.result = {};
                    else
                        msgbox(['Sorry, attempt to insert new ' in.des ...
                                ' failed; skipping to next sensor']);
                        continue
                    end
                else % status = 0 & in.val changed => typo in existing val
                    DT = datestr(DT, 'mmm DD YYYY');
                    where = ['EquipmentID=''' num2str(EID) ''' AND '...
                             'CAST(UTCCalibrationBeginDatetime AS NVARCHAR)'...
                             ' LIKE ''' DT '%'''];
                    try
                        q.setPropVals('where', {where})
                        q.submitStatement
                    catch ME
                        processCatch(q, ME)
                        continue
                    end
                    if isempty(q.result)
                        in.val = DT;
                        in.ttl = 'Calib. Date Still Not Found';
                        secondFailure(in)
                        continue
                    else
                        CID = q.result{:}; q.result = {};
                    end
                end
            end
%             try
%                 q.setPropVals('tbls', {'Calibration'})
%                 q.setPropVals('cols', {'CalibrationID'})
%                 DT = datestr(p.Sensors.(S).CalibrationDate', 'mmm DD YYYY');
%                 oldCond = ['EquipmentID=''' num2str(EID) ''''];
%                 newCond = ['CAST(UTCCalibrationBeginDatetime AS NVARCHAR) '...
%                            'LIKE ''' DT '%'''];
%                 q.setPropVals('where', {[oldCond ' AND ' newCond]})
%                 q.submitStatement
%             catch ME
%                 processCatch(q, ME)
%                 continue
%             end
%             if ~isdeployed && ~isempty(q.result)
%                 q.result = {};
%             end
%             if isempty(q.result)
%                 in.des = 'calibration date';
%                 in.val = p.Sensors.(S).CalibrationDate';
%                 in.ttl = 'Calibration Date Not Found';
%                 in.msg = ['The ' in.des ' ' DT ' for the ' S ', serial '...
%                           'number ' SN ' in the selected xmlcon was not '...
%                           'found in the DB. '];
%                 in.sn = SN;
%                 [result, in] = newORtypo(in);
%                 if isempty(result)
%                     continue
%                 else
%                     DT = result{:};
%                     if strcmp(DT, in.val) % in.val unchanged => new val
%                         I = Insert_dev(DB,struct('tbls',{{'Calibration'}}));
%                         in.wholeCond = q.where;
%                         q.setPropVals('where', {oldCond})
%                         status = processNew(I, q, in);
%                         I.errCode = {}; q.errCode = {};
%                         if status
%                             CID = q.result{:}; q.result = {};
%                         else
%                             msgbox(['Sorry, attempt to insert new ' in.des ...
%                                     ' failed; skipping to next sensor']);
%                             continue
%                         end
%                     else
%                         DT = datestr(result, 'mmm DD YYYY');
%                         where = ['CAST(UTCCalibrationBeginDatetime AS '...
%                                  'NVARCHAR) LIKE ''' DT '%'' AND '...
%                                  'EquipmentID=' num2str(EID)];
%                         try
%                             q.setPropVals('where', {where})
%                             q.submitStatement
%                         catch ME
%                             processCatch(q, ME)
%                             continue
%                         end
%                         if isempty(q.result)
%                             in.val = DT;
%                             in.ttl = 'Calibration Date Still Not Found';
%                             secondFailure(in)
%                             continue
%                         else
%                             CID = q.result{:}; q.result = {};
%                         end
%                     end
%                 end
%             else
%                 CID = q.result{:}; q.result = {};
%             end
            
          % Now, matching on CID, get CalibrationCoefficient.*; if data,
          % compare CalibrationCoefficientQuantities to those in
          % p.Sensors.(S), report any differences, and offer to change.
          % If no data, xmlcon CC's are new, display them and offer to push
          % If either offer is declined, continue on to next sensor
            try
                q.setPropVals('tbls', {'CalibrationCoefficient'})
                q.setPropVals('cols', {'*'})
                where = ['CalibrationID=' num2str(CID)];
                q.setPropVals('where', {where})
                q.submitStatement
            catch ME
                processCatch(q, ME)
                continue
            end
            if isempty(q.result)
                q.result
              % TODO: Calibration CCs not yet in DB--offer to process
            else
              % TODO: Calibration CCs in DB--offer to QAQC
            end
          
        end
    else
        errordlg(['Sorry, "' [DB.err{:}] '" trying to open EAPMW.  This error '...
                'is fatal, aborting.'], 'DB Error!', 'modal')
    end
    cleanUp;
end

function Write(varargin)
    BLOB2file(varargin{end-1}, varargin{end})
    varargin{end}.close;
    uiresume
end

function Examine(varargin)
    BLOB2file(varargin{end}, '.\temp.xmlcon')
    edit('.\temp.xmlcon');
end

function processCatch(q, ME)
  % TODO: finish these error messages
    if isempty(q.errCode)
        msg = ['Sorry, "' ME.message '" error encountered trying to ; '...
               'skipping to next sensor.'];
    else
        msg = ['Sorry, "' [q.errCode{:}] '" error encountered trying to ; '...
               'skipping to next sensor.'];
        q.errCode = {};
    end
    msgbox(msg, 'Alert: DB Error!');
end

function [out, in] = newORtypo(in)
    if isnumeric(in.val), in.val = num2str(in.val); end
    msg = [in.msg 'This may be due to a typo, or it may be a new ' in.des ...
           '. Your options: A) assume that it is new; B) assume that it '...
           'is a typo; C) check both alternatives; X) (i.e., closing this '...
           'dialog without making a selection) skip to the next sensor.'];
    reply = questdlg(msg, in.ttl, 'New value', 'Typo', 'Not sure',...
                     'Not sure');
    switch 1
        case strcmp(reply, 'New value')
            out = {in.val};
            return
            
        case strcmp(reply, 'Typo')
            msg = ['OK, here''s the ' in.des ' exactly as it occurs in the '...
                   'xmlcon--please correct it.'];
            ttl = [upper(in.des(1)) in.des(2:end) ' typo correction'];

        case strcmp(reply, 'Not sure')
            msg = ['OK, here''s the ' in.des ' exactly as it occurs in the '...
                   'in the xmlcon. If you think it''s a typo (i.e., repres'...
                   'ents a ' in.des ' that should already be associated '...
                   'with a ' in.sen ' in the DB) please correct it; other'...
                   'wise, just leave it be and I''ll treat it as a new '...
                   in.des ' for this sensor'];
            if isfield(in, 'sn')
                msg = [msg '/serial number'];
                if isfield(in, 'dt')
                    msg = [msg '/calibration date'];
                end
                msg = [msg ' combination'];
            end
            msg = [msg '.'];
            ttl = [upper(in.des(1)) in.des(2:end) ' check'];
            
        otherwise
            out = {}; return

    end
    out = inputdlg({msg}, ttl, 1, {in.val});
end

function secondFailure(in)
    msg = ['Sorry, we couldn''t find that ' in.des ' either for the '...
           in.sen];
    if regexp(in.des, 'calibration')
        msg = [msg ', serial number ' in.sn];
    end
    if regexp(in.des, 'coefficient')
        msg = [msg ', calibration date ' in.dt];
    end
    msg = [msg '--are you sure it''s not a new value for it? Either way, '...
           'I''m afraid you''ll have to reprocess this xmlcon (but before '...
           'you do, I suggest checking the source xmlcon manually). Skip'...
           'ping to next sensor.'];    
    msgbox(msg, ['Edited ' in.des ' not found']);
end

function status = processNew(I, q, in)
    status = 0; % Assume the worst
    cols = setdiff(I.availableColumns.(I.tbls{:}), I.pkey);
    try
        q.setPropVals('cols', cols)
        q.submitStatement
    catch ME
        processCatch(q, ME)
        return
    end
  % q.result empty => prior property was new also,
  % so its ID won't be in the current table yet
    if isempty(q.result)
        [col, val] = strtok(q.where,'=');
        I.setPropVals('cols', {col})
        I.setPropVals('data', struct(col, val(2:end-1)))
        I.submitStatement
        if ~isempty(I.errCode)
            return
        end
    end
    data = [q.result' q.result(end,:)'];
    rowNames = cols;
    editable = logical([zeros(1,size(q.result,1)) 1]);
    extantColName = '<html>Extant Value<br />(Non-editable)</html>';    
    colNames = [constCellStrArr(extantColName, [1,size(q.result',2)]),...
                '<html>New Value<br />(Editable)</html>'];
    colFmt = constCellStrArr('char', [1 size(q.result,1)+1]);
    switch 1
        case strcmp(q.tbls{:}, 'Equipment')
            data{strcmp(rowNames, 'SerialNumberDescription'), end} = in.val;
            EN = data{strcmp(rowNames, 'EquipmentName'), end};
            head = strtok(EN, ':');
            EN = [head ': ' in.val ')'];
            data{strcmp(rowNames, 'EquipmentName'), end} = EN;
            pK = 'EquipmentID'; % for later
        case strcmp(q.tbls{:}, 'Calibration')
            data(strcmp(rowNames,'UTCCalibrationBeginDatetime'),1:end-1) = ...
                cellfun(@datestr, data(strcmp(rowNames, ['UTCCalibration'...
                'BeginDatetime']),1:end-1), constCellStrArr('YYYY-mm-DD',...
                [1 size(data,2)-1]), 'UniformOutput', 0);
            data{strcmp(rowNames,'UTCCalibrationBeginDatetime'),end} = ...
                datestr(in.val, 'YYYY-mm-DD');
            pK = 'CalibrationID'; % for later
        case regexp(q.tbls{:}, 'Coefficient')
        otherwise
            return
    end
    F = figure('Units', 'Normalized',...
               'Position', [0.2 0.2 0.6 0.6]);
    P = uipanel(F, 'Units', 'normalized',...
                   'Position', [0 0 1 1]);
    T = uitable(P, 'Units', 'normalized',...
                   'Position', [0 0.1 1 0.9],...
                   'Data', data,... 
                   'RowName', rowNames,...
                   'ColumnEditable', editable,...
                   'ColumnName', colNames,...
                   'ColumnFormat', colFmt);
    B = uicontrol(P, 'Style', 'pushbutton',...
                     'Units', 'normalized',...
                     'Position', [0.45 0.02 0.1 0.06],...
                     'String', 'Done',...
                     'Callback', @newValDunCB);
    uiwait
    tmp = get(T, 'Data');
    close(F)
    clear B T P F
    tmp = tmp(:,end);
    try
        I.setPropVals('cols', cols(1))
        data = struct(cols{1}, tmp{1});
        for n=2:numel(cols)
            I.setPropVals('cols', cols(n), 1)
            data.(cols{n}) = tmp{n};
        end
        I.setPropVals('data', data)
        I.submitStatement
    catch ME
        processCatch(I, ME)
        return
    end
    if isempty(I.errCode)
        try
            q.setPropVals('cols', {pK})
            q.setPropVals('where', {in.wholeCond})
            q.submitStatement
            if isempty(q.errCode)
                status = 1;
            end
        catch ME
            processCatch(q, ME)
        end        
    end
end

function newValDunCB(varargin)
    uiresume
end

function cleanUp
    fclose('all');
    close('all');
    clear variables;
end
